/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.jasig.portal.portlet.dao.jpa;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.portlet.WindowState;

import org.apache.commons.lang.Validate;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.Type;
import org.jasig.portal.layout.dao.jpa.StylesheetDescriptorImpl;
import org.jasig.portal.layout.om.IStylesheetDescriptor;
import org.jasig.portal.portlet.om.IPortletDefinition;
import org.jasig.portal.portlet.om.IPortletDefinitionId;
import org.jasig.portal.portlet.om.IPortletEntity;
import org.jasig.portal.portlet.om.IPortletEntityId;
import org.jasig.portal.portlet.om.IPortletPreference;

/**
 * @author Eric Dalquist
 * @version $Revision$
 */
@Entity
@Table(name = "UP_PORTLET_ENT")
@SequenceGenerator(
        name="UP_PORTLET_ENT_GEN",
        sequenceName="UP_PORTLET_ENT_SEQ",
        allocationSize=10
    )
@TableGenerator(
        name="UP_PORTLET_ENT_GEN",
        pkColumnValue="UP_PORTLET_ENT",
        allocationSize=10
    )
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
class PortletEntityImpl implements IPortletEntity {
    //Properties are final to stop changes in code, hibernate overrides the final via reflection to set their values
    @Id
    @GeneratedValue(generator = "UP_PORTLET_ENT_GEN")
    @Column(name = "PORTLET_ENT_ID")
    private final long internalPortletEntityId;
    
    @SuppressWarnings("unused")
    @Version
    @Column(name = "ENTITY_VERSION")
    private final long entityVersion;
    
    @NaturalId
    @Column(name = "LAYOUT_NODE_ID", nullable = false)
    private final String layoutNodeId;

    @NaturalId
    @Index(name = "IDX_UP_PORTLET_ENT__UP_USER")
    @Column(name = "USER_ID", nullable = false)
    private final int userId;

    //Hidden reference to the parent portlet definition, used by hibernate for referential integrety
    @ManyToOne(targetEntity = PortletDefinitionImpl.class, cascade = {  CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
    @JoinColumn(name = "PORTLET_DEF_ID", nullable = false)
    private final IPortletDefinition portletDefinition;
    
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(name="UP_PORTLET_ENT__STATES", joinColumns=@JoinColumn(name="PORTLET_ENT_ID"))
    @MapKeyJoinColumn(name="STYLESHEET_DESCRIPTOR_ID")
    @Column(name = "WINDOW_STATE")
    @Type(type = "windowState")
    private final Map<StylesheetDescriptorImpl, WindowState> windowStates = new HashMap<StylesheetDescriptorImpl, WindowState>(0);

    @OneToOne(cascade = { CascadeType.ALL }, orphanRemoval=true)
    @JoinColumn(name = "PORTLET_PREFS_ID", nullable = false)
    @Fetch(FetchMode.JOIN)
    private final PortletPreferencesImpl portletPreferences;

    @Transient
    private IPortletEntityId portletEntityId = null;
    
    /**
     * Used to initialize fields after persistence actions.
     */
    @SuppressWarnings("unused")
    @PostLoad
    @PostPersist
    @PostUpdate
    @PostRemove
    private void init() {
        this.portletEntityId = new PortletEntityIdImpl(this.internalPortletEntityId);
    }
    
    /**
     * Used by the ORM layer to create instances of the object.
     */
    @SuppressWarnings("unused")
    private PortletEntityImpl() {
        this.internalPortletEntityId = -1;
        this.entityVersion = -1;
        this.portletDefinition = null;
        this.layoutNodeId = null;
        this.userId = -1;
        this.portletPreferences = null;
    }
    
    public PortletEntityImpl(IPortletDefinition portletDefinition, String channelSubscribeId, int userId) {
        Validate.notNull(portletDefinition, "portletDefinition can not be null");
        Validate.notNull(channelSubscribeId, "layoutNodeId can not be null");
        
        this.internalPortletEntityId = -1;
        this.entityVersion = -1;
        this.portletDefinition = portletDefinition;
        this.layoutNodeId = channelSubscribeId;
        this.userId = userId;
        this.portletPreferences = new PortletPreferencesImpl();
    }

    @Override
    public IPortletDefinitionId getPortletDefinitionId() {
        return this.portletDefinition.getPortletDefinitionId();
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.om.portlet.IPortletEntity#getPortletEntityId()
     */
    @Override
    public IPortletEntityId getPortletEntityId() {
        return this.portletEntityId;
    }
    
    @Override
    public IPortletDefinition getPortletDefinition() {
        return this.portletDefinition;
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.om.portlet.IPortletEntity#getChannelSubscribeId()
     */
    @Override
    public String getLayoutNodeId() {
        return this.layoutNodeId;
    }

    /* (non-Javadoc)
     * @see org.jasig.portal.om.portlet.IPortletEntity#getUserId()
     */
    @Override
    public int getUserId() {
        return this.userId;
    }
    
    
    @Override
    public Map<Long, WindowState> getWindowStates() {
        final Map<Long, WindowState> simpleWindowStates = new LinkedHashMap<Long, WindowState>();
        synchronized (this.windowStates) {
            for (Map.Entry<StylesheetDescriptorImpl, WindowState> windowStateEntry : windowStates.entrySet()) {
                final StylesheetDescriptorImpl stylesheetDescriptor = windowStateEntry.getKey();
                final long stylesheetDescriptorId = stylesheetDescriptor.getId();
                final WindowState windowState = windowStateEntry.getValue();
                simpleWindowStates.put(stylesheetDescriptorId, windowState);
            }
        }
        return Collections.unmodifiableMap(simpleWindowStates);
    }

    @Override
    public WindowState getWindowState(IStylesheetDescriptor stylesheetDescriptor) {
        synchronized (this.windowStates) {
            return this.windowStates.get(stylesheetDescriptor);
        }
    }

    @Override
    public void setWindowState(IStylesheetDescriptor stylesheetDescriptor, WindowState state) {
        synchronized (this.windowStates) {
            if (state == null) {
                this.windowStates.remove(stylesheetDescriptor);
            }
            else {
                this.windowStates.put((StylesheetDescriptorImpl)stylesheetDescriptor, state);
            }
        }
    }

    /* (non-Javadoc)
	 * @see org.jasig.portal.portlet.om.IPortletEntity#getPortletPreferences()
	 */
	@Override
	public List<IPortletPreference> getPortletPreferences() {
		return portletPreferences.getPortletPreferences();
	}

	/* (non-Javadoc)
	 * @see org.jasig.portal.portlet.om.IPortletEntity#setPortletPreferences(java.util.List)
	 */
	@Override
	public boolean setPortletPreferences(List<IPortletPreference> portletPreferences) {
		return this.portletPreferences.setPortletPreferences(portletPreferences);
	}

	@Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((this.layoutNodeId == null) ? 0 : this.layoutNodeId.hashCode());
        result = prime * result + ((this.portletDefinition == null) ? 0 : this.portletDefinition.hashCode());
        result = prime * result + this.userId;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!IPortletEntity.class.isAssignableFrom(obj.getClass()))
            return false;
        IPortletEntity other = (IPortletEntity) obj;
        if (this.layoutNodeId == null) {
            if (other.getLayoutNodeId() != null)
                return false;
        }
        else if (!this.layoutNodeId.equals(other.getLayoutNodeId()))
            return false;
        if (this.portletDefinition == null) {
            if (other.getPortletDefinition() != null)
                return false;
        }
        else if (!this.portletDefinition.equals(other.getPortletDefinition()))
            return false;
        if (this.userId != other.getUserId())
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "PortletEntity [" +
                "portletEntityId=" + this.portletEntityId + ", " +
                "layoutNodeId=" + this.layoutNodeId + ", " +
                "userId=" + this.userId + ", " +
                "portletDefinition=" + this.portletDefinition + "]";
    }
}
